// 阻塞队列  模拟实现消费者和生产者模型
#pragma once
#include <iostream>
#include <pthread.h>
#include <queue>
#include "mymutex.hpp"

template<class T>
class blockqueue
{
public:
    blockqueue(const size_t capacity = 1024)
    :_capacity(capacity)
    {
        _bq = new std::queue<T>();
        // 初始化互斥锁和条件变量
        pthread_mutex_init(&_mtx, nullptr);
        pthread_cond_init(&_full, nullptr);
        pthread_cond_init(&_empty, nullptr);
    }

    blockqueue(const blockqueue&) = delete;  // 禁止拷贝阻塞队列对象

    // 生产者写入数据
    void push(const T& val)
    {
        // 上锁和解锁我们可以利用一个优雅的方式 RAII
        mutexguard mtx(&_mtx);
        // 判断临界资源是否满足条件，满足在进行写入
        while (_capacity == _bq->size()) pthread_cond_wait(&_full, &_mtx);  // 注意这种方式容易出现问题：如果wait等待失败的话，那么就不会阻塞，继续进行下一步，所以需要while循环等待
        _bq->push(val);
        // 注意此时生成者写入数据了，就可以告诉消费者可以取数据了
        pthread_cond_signal(&_empty);
    }

    // 消费者获得数据
    void pop(T* arg)  // 输出型参数
    {
        mutexguard mtx(&_mtx);
        while (0 == _bq->size()) pthread_cond_wait(&_empty, &_mtx);
        *arg = _bq->front();
        _bq->pop();
        pthread_cond_signal(&_full);
    }

    ~blockqueue()
    {
        // 析构函数释放对应的资源
        delete _bq;
        pthread_mutex_destroy(&_mtx);
        pthread_cond_destroy(&_full);
        pthread_cond_destroy(&_empty);
    }

private:
    std::queue<T>* _bq;     // 阻塞队列自然用STL库中的队列实现     -注意此队列接口一般是不可重入函数，即存在线程安全问题，需要我们自己去保障
    size_t _capacity;       // 容量大小
    pthread_mutex_t _mtx;  // 互斥锁 -互斥
    pthread_cond_t _full;  // 条件变量 -同步 满
    pthread_cond_t _empty; // 空
};